package ru.usu.gv.core;

import com.sun.j3d.utils.universe.SimpleUniverse;

import ru.usu.gv.gui.GUIEngine;
import ru.usu.gv.gui.actions.AddGraphAction;
import ru.usu.gv.styles.StyleFacade;
import ru.usu.gv.uicomponents.UIGraph;
import ru.usu.gv.utils.imports.GraphImporter;

import javax.media.j3d.*;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;

/**
 * @author: Sergey Pupyrev 
 * Apr 2, 2006
 */

public class GraphCanvas extends Canvas3D
{
	private static GraphCanvas INSTANCE = null;

	private SimpleUniverse su;
	private BranchGroup objRoot;
	private TransformGroup transformGroup;
	private BoundingSphere bounds;

	private BranchGroup rootBranchGroup;

	private UIGraph currentGraph;

	public static GraphCanvas getInstance()
	{
		return INSTANCE;
	}

	public SimpleUniverse getUniverse()
	{
		return su;
	}

	public BoundingSphere getBoundingSphere()
	{
		return bounds;
	}

	public UIGraph getCurrentGraph()
	{
		return currentGraph;
	}

	public void setCurrentGraph(UIGraph graph)
	{
		currentGraph = graph;
		GUIEngine.getInstance().setTitle(graph.getId());
	}

	public GraphCanvas()
	{
		super(SimpleUniverse.getPreferredConfiguration());

		if (INSTANCE != null)
			throw new RuntimeException("Multiple instance of GraphCanvas created!");

		INSTANCE = this;

		su = new SimpleUniverse(this);
		su.getViewingPlatform().setNominalViewingTransform();
		su.getViewer().getView().setTransparencySortingPolicy(View.TRANSPARENCY_SORT_NONE);
		su.getViewer().getView().setDepthBufferFreezeTransparent(false);

		BranchGroup scene = createSceneGraph();

		GraphEngine.getInstance().init();
		scene.compile();

		su.addBranchGraph(scene);
	}

	private BranchGroup createSceneGraph()
	{
		bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1000.0);

		objRoot = new BranchGroup();
		objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

		transformGroup = new TransformGroup();
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		transformGroup.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
		transformGroup.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
		transformGroup.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
		objRoot.addChild(transformGroup);

		addBackground();

		addLight();

		rootBranchGroup = createRootBranchGroup();
		transformGroup.addChild(rootBranchGroup);

		try
		{
			setCurrentGraph(new UIGraph("Empty graph"));
			new AddGraphAction(GraphImporter.loadGraph("sample.gml")).execute();
		}
		catch (Exception e)
		{
			System.err.println("Error during importing graph: " + e.getMessage());
			setCurrentGraph(new UIGraph("Empty graph"));
		}

		return objRoot;
	}

	private BranchGroup createRootBranchGroup()
	{
		BranchGroup rootBranchGroup = new BranchGroup();
		rootBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
		rootBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		rootBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		return rootBranchGroup;
	}

	private void addBackground()
	{
		Background background = new Background(new Color3f(1, 1, 1));
		background.setApplicationBounds(bounds);
		background.setCapability(Background.ALLOW_COLOR_WRITE);
		background.setCapability(Background.ALLOW_COLOR_READ);
		background.setCapability(Background.ALLOW_IMAGE_WRITE);
		objRoot.addChild(background);
	}

	private void addLight()
	{
		Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
		AmbientLight ambientLightNode = new AmbientLight(ambientColor);
		ambientLightNode.setInfluencingBounds(bounds);
		objRoot.addChild(ambientLightNode);

		// Set up the directional lights
		Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
		Vector3f light1Direction = new Vector3f(4.0f, -7.0f, -12.0f);
		Color3f light2Color = new Color3f(0.3f, 0.3f, 0.4f);
		Vector3f light2Direction = new Vector3f(-6.0f, -2.0f, -1.0f);

		DirectionalLight light1 = new DirectionalLight(light1Color, light1Direction);
		light1.setInfluencingBounds(bounds);
		objRoot.addChild(light1);

		DirectionalLight light2 = new DirectionalLight(light2Color, light2Direction);
		light2.setInfluencingBounds(bounds);
		objRoot.addChild(light2);
	}

	public void clear()
	{
		GraphEngine.getInstance().getUIGraphListeners().doChange(currentGraph);
		GraphEngine.getInstance().getGraphListeners().doChange(currentGraph.getGraph());

		//For gc
		rootBranchGroup.detach();
		rootBranchGroup = null;
		currentGraph = null;

		rootBranchGroup = createRootBranchGroup();
		transformGroup.addChild(rootBranchGroup);
	}

	public void addChild(BranchGroup branchGroup)
	{
		rootBranchGroup.addChild(branchGroup);
	}

	public BranchGroup getObjRoot()
	{
		return objRoot;
	}

	public TransformGroup getTransformGroup()
	{
		return transformGroup;
	}

	public Behavior addCustomBehavior(Behavior behavior)
	{
		behavior.setSchedulingBounds(bounds);
		behavior.setEnable(false);
		transformGroup.addChild(behavior);
		return behavior;
	}

	//TODO: re-move axis
	private BranchGroup axisBranchGroup;

	public void setAxisVisible(boolean visible)
	{
		if (visible)
		{
			if (axisBranchGroup == null)
			{
				axisBranchGroup = new BranchGroup();
				axisBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
				axisBranchGroup.setBounds(bounds);
				float radius = (float) StyleFacade.getDefaultDoubleAttribute("axis.radius");
				float height = (float) StyleFacade.getDefaultDoubleAttribute("axis.height");

				axisBranchGroup.addChild(createAxis(radius, new Point3d(-height, 0, 0), new Point3d(height, 0, 0)));
				axisBranchGroup.addChild(createAxis(radius, new Point3d(0, -height, 0), new Point3d(0, height, 0)));
				axisBranchGroup.addChild(createAxis(radius, new Point3d(0, 0, -height), new Point3d(0, 0, height)));
			}

			addChild(axisBranchGroup);
		}
		else
		{
			if (axisBranchGroup != null)
			{
				axisBranchGroup.detach();
				axisBranchGroup = null;
			}
		}
	}

	private Shape3D createAxis(float radius, Point3d p1, Point3d p2)
	{
		Shape3D shape = new Shape3D();
		//appearance
		Color3f col = new Color3f(0.2f, 0.85f, 0.6f);
		ColoringAttributes ca = new ColoringAttributes(col, ColoringAttributes.NICEST);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(ca);
		shape.setAppearance(appearance);
		shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
		shape.setCapability(Shape3D.ALLOW_BOUNDS_WRITE);

		//geometry
		Point3d[] coordinates = new Point3d[] { p1, p2 };
		Geometry geometry = new LineStripArray(coordinates.length, GeometryArray.COORDINATES, new int[] { coordinates.length });
		((LineStripArray) geometry).setCoordinates(0, coordinates);
		shape.setGeometry(geometry);
		shape.setBounds(bounds);
		return shape;
	}

}
